﻿// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions at http://help.arcgis.com/en/sdk/10.0/usageRestrictions.htm
// 

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using ESRI.ArcGIS.ADF.Web.UI.WebControls;
using System.Reflection;
using System.Text;
using ESRI.ArcGIS.ADF.Web.Geometry;
using ESRI.ArcGIS.ADF.Web.Display.Graphics;
using ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools;
using ESRI.ArcGIS.ADF.Web.DataSources;

public partial class UserControls_FindNearTask : UserControlTaskPanel
{
    #region Properties
    #region Member Variables
    private StateManager StateManager;
    private ScriptManager scriptManager = null;
    // Internal references to buddied Map and TaskResults controls
    //ESRI.ArcGIS.ADF.Web.UI.WebControls.Map _map = null;
    ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults _taskResults = null;
    #endregion

    #region Internal Properties
    // The task's input parameters.  See InputParameters.cs for the parameters' class definition.
    //internal InputParameters TaskInput
    //{
    //    get
    //    {
    //        // Attempt to retrieve the input parameters from state
    //        InputParameters inputParameters = StateManager.GetProperty("FindNearTaskInputParameters") as InputParameters;
    //        if (inputParameters == null)
    //        {
    //            // Since no parameters were stored in state, create a new InputParameters object
    //            inputParameters = new InputParameters();
    //            // Initialize the TaskInput property so the InputParameters object is stored in state
    //            this.TaskInput = inputParameters;
    //        }
    //        // Return the parameters
    //        return inputParameters;
    //    }
    //    set
    //    {
    //        // Store the passed-in parameters in state
    //        StateManager.SetProperty("FindNearTaskInputParameters", value);
    //    }
    //}

    // Graphics resource used to store user input and buffer graphics layers.  The geometries of the features
    // in this resource are used as input for the task.
    internal GraphicsResource TaskInputGraphicsResource
    {
        get
        {
            // Attempt to retrieve the task's graphics resource from state
            GraphicsResource taskGraphicsResource =
                this.StateManager.GetProperty("TaskInputGraphicsResource") as GraphicsResource;
            if (taskGraphicsResource == null)
            {
                // Since the task's graphics resource is not yet in state, create it and store it
                string resourceName = string.Format("{0} Input Graphics Resource", this.ClientID);
                taskGraphicsResource = new GraphicsResource(resourceName, this.MapInstance);
                this.StateManager.SetProperty("TaskInputGraphicsResource", taskGraphicsResource);
            }

            // Return the resource
            return taskGraphicsResource;
        }
    }

    #endregion
    
    #region Private Properties
    // Stores the geometries that the user is working with
    private Geometry[] InputGeometries
    {
        get { return StateManager.GetProperty("bufferGeometry") as Geometry[]; }
        set { StateManager.SetProperty("bufferGeometry", value); }
    }

    // Returns the buddied Map
    private ESRI.ArcGIS.ADF.Web.UI.WebControls.Map MapInstance
    {
        get
        {
            return this.TaskResultsInstance.MapInstance;
        }
    }

    // Returns the first buddied TaskResults container that has a buddied Map
    private ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults TaskResultsInstance
    {
        get
        {
            // Check whether the internal task results variable is null
            if (_taskResults == null)
            {
                _taskResults = Page.FindControl(TaskResults) as TaskResults;                
            }
            // Return the internal variable storing the reference to the TaskResults
            return _taskResults;
        }
    }

    // Counts the number of times the task has executed.  Used to apply a unique ID to new TaskResultsPanels displaying resutls.
    private int SearchCount
    {
        get
        {
            if (this.StateManager.GetProperty("OperationCount") == null)
                return 1;
            else
                return (int)this.StateManager.GetProperty("OperationCount");
        }
        set
        {
            this.StateManager.SetProperty("OperationCount", value);
        }
    }

    // ID of the Map control
    private string Map
    {
        get
        {
            return this.TaskResultsInstance.MapInstance.ID;
        }
    }

    private string TaskResults
    {
        get
        {
            return "TaskResults1";
        }
    }
    #endregion

    #region Public Properties

    /// <summary>
    /// The URL of an ArcGIS Server Geometry Service.  Required for calculating the search area.
    /// </summary>
    [System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute)]
    [System.ComponentModel.Browsable(true)]
    [System.ComponentModel.NotifyParentProperty(true)]
    [System.ComponentModel.DefaultValue("http://localhost/arcgis/services/Geometry/GeometryServer")]
    [System.ComponentModel.Description("URL for an ArcGIS Server geometry service")]
    [System.ComponentModel.Category("Task")]
    public string GeometryServiceUrl
    {
        get
        {
            // Attempt retrieving the geometry service URL from state.  If not found, return the default
            // URL.  Otherwise, return the value from state.
            object obj = StateManager.GetProperty("GeometryServiceUrl");
            return (obj == null) ? "http://localhost/arcgis/services/Geometry/GeometryServer" : (string)obj;
        }
        // Store the passed-in value in state
        set { StateManager.SetProperty("GeometryServiceUrl", value); }
    }

    #endregion

    #region Page Events    
    protected void Page_Load(object sender, EventArgs e)
    {        
        string assemName = Assembly.GetAssembly(typeof(SearchAreaByPoint)).FullName;
        foreach (ToolbarItem item in toolbar.ToolbarItems)
        {
            if (item is InteractiveToolbarItem)
            {
                (item as InteractiveToolbarItem).ServerActionAssembly = assemName;
            }
        }

        scriptManager = ScriptManager.GetCurrent(Page);
        StateManager = new StateManager(this.ClientID, Page.AppRelativeVirtualPath);
        if (scriptManager != null && !scriptManager.IsInAsyncPostBack)
        {
            loadLayersDropDown();            
        }
        this.TaskResultsInstance.NodeClicked +=
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeClickedEventHandler(TaskResults_NodeClicked);
    }

    protected void Page_PreRender(object sender, EventArgs e)
    {
        if (scriptManager != null && !scriptManager.IsInAsyncPostBack)
        {
            scriptManager.Scripts.Add(new ScriptReference(Page.ResolveClientUrl("~/FindNearTask/javascript/FindNearTask.js")));                        
            StringBuilder sb = new StringBuilder("Sys.Application.add_init(function(){");
            sb.AppendFormat("$create(FindNearTask,{{'divSelectionLayerID':'{2}','hdnSearchAreaByTaskResultID':'{3}'}},null,{{'toolbar':'{1}'}},$get('{0}'));", container.ClientID, toolbar.ClientID, divSelectionLayer.ClientID, hdnSearchAreaByTaskResult.ClientID);
            sb.Append("});");
            ScriptManager.RegisterStartupScript(this, typeof(UserControls_FindNearTask), ClientID + "_startup", sb.ToString(), true);
        }
    }   

    protected void TaskResults_NodeClicked(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs args)
    {
        // Whether to define the search area by selecting a task result
        bool isSearchAreaByTaskResultEnabled = "true".Equals(hdnSearchAreaByTaskResult.Value);
        // Check whether the passed-in node is a feature node and the define search area by task result tool is active
        if (isSearchAreaByTaskResultEnabled && (args.Node is ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode))
        {
            // Get a reference to the clicked node as a FeatureNode
            ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode clickedFeatureNode = args.Node as
                ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode;

            // Loop through the data of the FeatureNode until the feature geometry is found
            foreach (object value in clickedFeatureNode.DataRow.ItemArray)
            {
                if (value is ESRI.ArcGIS.ADF.Web.Geometry.Geometry)
                {
                    // Put the feature geometry in a geometry array
                    ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] inputGeometries =
                        new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[1];
                    inputGeometries[0] = value as ESRI.ArcGIS.ADF.Web.Geometry.Geometry;

                    if (InputGeometries == null)
                        InputGeometries = new Geometry[0];

                    if (chkAddToSelection.Checked)
                    {
                        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] combinedGeometries =
                            new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[inputGeometries.Length + InputGeometries.Length];

                        // Copy the previously defined geometries to the new array
                        InputGeometries.CopyTo(combinedGeometries, 0);
                        // Add the passed-in geometries to the end of the new array
                        for (int i = InputGeometries.Length; i < combinedGeometries.Length; i++)
                            combinedGeometries[i] = inputGeometries[i - InputGeometries.Length];

                        // Overwrite the previous geometry array with the combined array
                        InputGeometries = combinedGeometries;
                    }
                    else
                    {
                        InputGeometries = inputGeometries;
                    }

                    string searchDistance = txtDistance.Text;
                    float newSearchDistance;
                    float.TryParse(searchDistance, out newSearchDistance);

                    string[] searchLayerParameters = ddlLayers.SelectedValue.Split(new char[] { ',' });
                    string searchResource = searchLayerParameters[0];
                    string searchLayer = searchLayerParameters[1];

                    // Buffer the geometry 
                    updateInputGraphicsLayers(InputGeometries, newSearchDistance, ddlUnits.SelectedValue);

                    // Update the resource containing the input graphics layers
                    this.MapInstance.RefreshResource(this.TaskInputGraphicsResource.ResourceName);                    
                    break;
                }
            }
        }
    }
    #endregion
    #endregion

    #region Task Implementation
    protected void doFind(object sender, EventArgs e)
    {
        Start(ddlLayers.SelectedValue);
    }

    public override object ExecuteTask(object parameters)
    {
        if (InputGeometries == null || InputGeometries.Length == 0)
            return null;

        #region Get Search Parameters
        string[] searchLayerParameters = (parameters as string).Split(new char[] { ',' });
        string searchResource = searchLayerParameters[0];
        string searchLayer = searchLayerParameters[1];
        #endregion

        #region Do the Query
        // Get the buddied map resource manager
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceManager mapResourceManager =
            this.MapInstance.MapResourceManagerInstance;
        // Get the map resource item containing the resource to be searched
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem mapResourceItem =
        mapResourceManager.ResourceItems.Find(searchResource);        

        // Get a reference to the resource item's map resource
        ESRI.ArcGIS.ADF.Web.DataSources.IMapResource commonMapResource =
            mapResourceItem.Resource as ESRI.ArcGIS.ADF.Web.DataSources.IMapResource;

        // Use the resource to create a query functionality
        string queryFunctionalityName = string.Format("{0}_QueryFunctionality", this.ID);
        ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality queryFunctionality =
            commonMapResource.CreateFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality),
            queryFunctionalityName) as ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality;

        // Instantiate a spatial filter with the buffer geometry
        ESRI.ArcGIS.ADF.Web.SpatialFilter adfSpatialFilter = new ESRI.ArcGIS.ADF.Web.SpatialFilter();
        adfSpatialFilter.Geometry = InputGeometries[0]; // this.TaskInput.BufferGeometry;
        // Specify that the query return the geometries of result features
        adfSpatialFilter.ReturnADFGeometries = true;
        // Set the spatial reference of the query results to match that of the map
        adfSpatialFilter.OutputSpatialReference = MapInstance.SpatialReference;
        
        // Execute the query
        System.Data.DataTable resultsDataTable =
            queryFunctionality.Query(null, searchLayer, adfSpatialFilter);

        // Make sure results were found and they can be shown as a graphics layer
        if (resultsDataTable == null || !(resultsDataTable is
        ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer))
            return resultsDataTable;

        // Convert query results to a graphics layer
        ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer resultsGraphicsLayer =
            ESRI.ArcGIS.ADF.Web.Converter.ToGraphicsLayer(resultsDataTable);

        // Retrieve and apply the layer format of the search layer to the results
        ESRI.ArcGIS.ADF.Web.UI.WebControls.LayerFormat layerFormat =
            ESRI.ArcGIS.ADF.Web.UI.WebControls.LayerFormat.FromMapResourceManager(mapResourceManager,
            searchResource, searchLayer);
        layerFormat.Apply(resultsGraphicsLayer);

        // Set RenderOnClient to true so highlighting and maptips are enabled on the results
        resultsGraphicsLayer.RenderOnClient = true;

        // Select all features so that they are visible by default
        foreach (System.Data.DataRow dataRow in resultsGraphicsLayer.Rows)
            dataRow[resultsGraphicsLayer.IsSelectedColumn] = true;

        // Create a graphics dataset and add the results graphics layer to it
        ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet resultsGraphicsDataSet =
            new ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet();
        resultsGraphicsDataSet.DataSetName = string.Format("Kết quả ({0})", resultsGraphicsLayer.Rows.Count);
        resultsGraphicsDataSet.Tables.Add(resultsGraphicsLayer);

        // Get the number of searches performed and append the ordinal suffix 
        string searchCount = this.SearchCount.ToString();
        string searchCountDescription;
        switch (searchCount.Substring(searchCount.Length - 1))
        {
            case "1":
                searchCount += "st";
                break;
            case "2":
                searchCount += "nd";
                break;
            case "3":
                searchCount += "rd";
                break;
            default:
                searchCount += "th";
                break;
        }
        // Create a count description with the search count
        searchCountDescription = string.Format("{0} Tìm", searchCount);
        // Create the results GraphicsLayer's table name from the ADF generated table name, the number of
        // rows, and the search count string
        resultsGraphicsLayer.TableName = string.Format("{0} ({1} tìm thấy, {2})", resultsGraphicsLayer.TableName,
            resultsGraphicsLayer.Rows.Count, searchCountDescription);
        // increment the search count
        this.SearchCount++;
        #endregion

        #region Return Result DataSet
        return resultsGraphicsDataSet;
        #endregion
    }
    #endregion

    #region Routine called by Tools
    internal void OnToolbarItemAction(string toolName, Geometry[] inputGeometries)
    {
        if ("ClearInput".Equals(toolName))
        {
            // Retrieve and clear the user input and buffer graphics layers
            ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer elementGraphicsLayer =
                this.TaskInputGraphicsResource.GetUserInputLayer(this.MapInstance.MapResourceManagerInstance);
            elementGraphicsLayer.Clear();

            elementGraphicsLayer = this.TaskInputGraphicsResource.GetBufferLayer(
                this.MapInstance.MapResourceManagerInstance);
            elementGraphicsLayer.Clear();

            // Update the resource containing the input graphics layers
            this.MapInstance.RefreshResource(this.TaskInputGraphicsResource.ResourceName);

            // Clear results from session
            InputGeometries = new Geometry[0];

            return;
        }

        if(InputGeometries == null)
            InputGeometries = new Geometry[0];

        if (chkAddToSelection.Checked)
        {
            ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] combinedGeometries =
                new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[inputGeometries.Length + InputGeometries.Length];

            // Copy the previously defined geometries to the new array
            InputGeometries.CopyTo(combinedGeometries, 0);
            // Add the passed-in geometries to the end of the new array
            for (int i = InputGeometries.Length; i < combinedGeometries.Length; i++)
                combinedGeometries[i] = inputGeometries[i - InputGeometries.Length];

            // Overwrite the previous geometry array with the combined array
            InputGeometries = combinedGeometries;
        }
        else
        {
            InputGeometries = inputGeometries;
        }

        string searchDistance = txtDistance.Text;
        float newSearchDistance;
        float.TryParse(searchDistance, out newSearchDistance);        

        string[] searchLayerParameters = ddlLayers.SelectedValue.Split(new char[] { ',' });
        string searchResource = searchLayerParameters[0];
        string searchLayer = searchLayerParameters[1];

        // Buffer the geometry 
        updateInputGraphicsLayers(InputGeometries,  newSearchDistance, ddlUnits.SelectedValue);
        
        // Refresh the resource containing the input graphics layer
        MapInstance.RefreshResource(this.TaskInputGraphicsResource.ResourceName);
    }    
    
    #endregion

    #region Utility Functions
    #region Layers DropDown
    private void loadLayersDropDown()
    {
        MapResourceManager mgr = (Page.FindControl(Map) as Map).MapResourceManagerInstance;

        // Dictionaries to hold the names and IDs of queryable layers
        System.Collections.Generic.Dictionary<string, string[]> layerNamesByResourceDictionary;
        System.Collections.Generic.Dictionary<string, string[]> layerIDsByResourceDictionary;

        // Get the names and IDs of all the queryable layers referenced by the buddied map resource manager
        this.GetQueryableLayers(MapInstance.MapResourceManagerInstance, out layerNamesByResourceDictionary,
            out layerIDsByResourceDictionary);

        // Loop through all the items in the layer names dictionary, adding each to the search layer and 
        // selection layer lists
        foreach (System.Collections.Generic.KeyValuePair<string, string[]> layerNamesPair in
            layerNamesByResourceDictionary)
        {
            // Get the name of the resource from the current key value pair
            string resourceName = layerNamesPair.Key;

            // Get the array of layer names for the current resource
            string[] layerNamesArray = layerNamesPair.Value;

            // Loop through the names, creating a tree view plus node below the current resource node for each
            for (int i = 0; i < layerNamesArray.Length; i++)
            {
                // Get the layer ID for the current layer
                string layerID = layerIDsByResourceDictionary[resourceName][i];
                
                // Create an item for the current layer and add it to the selection layer and search layer 
                // drop-down lists.  Specify the item's text as the layer name, and the item's value as the
                // name of the resource containing the layer and the layer ID, delimited by a comma.
                System.Web.UI.WebControls.ListItem layerListItem = new System.Web.UI.WebControls.ListItem();
                layerListItem.Text = layerNamesArray[i];
                layerListItem.Value = string.Format("{0},{1}", resourceName, layerID);

                ddlSelectionLayers.Items.Add(layerListItem);
                ddlLayers.Items.Add(layerListItem);
            }
        }

        // Get the map functionality for the primary map resource
        ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality primaryResourceMapFunctionality =
            this.MapInstance.GetFunctionality(MapInstance.PrimaryMapResource);
        string defaultUnit = primaryResourceMapFunctionality.Units.ToString();
        if ("Unknown".Equals(defaultUnit) || "DecimalDegrees".Equals(defaultUnit))
            defaultUnit = "Kilometers";
        ddlUnits.SelectedValue = defaultUnit;
    }

    // Retrieves queryable layers from all resources in the passed-in MapResourceManager
    private void GetQueryableLayers(ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceManager mapResourceManager,
        out System.Collections.Generic.Dictionary<string, string[]> layerNamesByResources,
        out System.Collections.Generic.Dictionary<string, string[]> layerIDsByResources)
    {
        layerNamesByResources = new System.Collections.Generic.Dictionary<string, string[]>();
        layerIDsByResources = new System.Collections.Generic.Dictionary<string, string[]>();

        bool initializedResourceManager = false;
        try
        {
            // If the MapResourceManager is not initialize, do so here
            if (!mapResourceManager.Initialized)
            {
                mapResourceManager.Initialize();
                initializedResourceManager = true;
            }

            ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem mapResourceItem;

            // Loop through the resources in the MapResourceManager
            for (int i = 0; i < mapResourceManager.ResourceItems.Count; i++)
            {
                mapResourceItem = mapResourceManager.ResourceItems[i];

                // If the current resource is not displayed in the Toc, skip to the next
                if (!mapResourceItem.DisplaySettings.DisplayInTableOfContents)
                    continue;

                // Ensure the resource is initialized
                mapResourceItem.InitializeResource();

                // Get the map resource from the resource item
                ESRI.ArcGIS.ADF.Web.DataSources.IMapResource commonMapResource =
                    mapResourceItem.Resource as ESRI.ArcGIS.ADF.Web.DataSources.IMapResource;
                if (commonMapResource == null)
                    continue;

                // If the resource does not support querying, skip to the next
                if (!commonMapResource.SupportsFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality)))
                    continue;

                // Create a query functionality form the resource
                ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality commonQueryFunctionality = commonMapResource.CreateFunctionality(
                    typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality), System.Guid.NewGuid().ToString("N"))
                    as ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality;

                // Get arrays containing the resources queryable layer names and IDs
                string[] layerIDs = null;
                string[] layerNames = null;
                commonQueryFunctionality.GetQueryableLayers(null, out layerIDs, out layerNames);

                // Add the arrays to the layer names and IDs dictionaries, along with the name of the current resource item
                layerNamesByResources.Add(mapResourceItem.Name, layerNames);
                layerIDsByResources.Add(mapResourceItem.Name, layerIDs);
            }
        }
        catch { }
        finally
        {
            // If the MapResourceManager was initialized in this method, call Dispose to restore it to its initial state
            if (initializedResourceManager)
                mapResourceManager.Dispose();
        }

        return;
    }
    #endregion

    #region GeoSpatial Utility Methods
    private Geometry updateInputGraphicsLayers(Geometry[] inputGeometries, float searchDistance, string units)
    {
        // If there are not defined input geometries, exit the function
        if (inputGeometries == null || inputGeometries.Length == 0)
            return null;

        Geometry bufferGeometry = null;
        // Get the graphics layer displaying the user defined geometries and clear it
        ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer inputGraphicsLayer =
            this.TaskInputGraphicsResource.GetUserInputLayer(this.MapInstance.MapResourceManagerInstance)
            as ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer;
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] adfSearchGeometries = inputGeometries;
        inputGraphicsLayer.Clear();

        // Make sure the user specified geometries and distance are valid
        if (adfSearchGeometries != null && !float.IsNaN(searchDistance) && searchDistance > 0.0)
        {
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicElement inputGraphicElement = null;

            // Create a symbol for the user specified geometry
            for (int i = 0; i < adfSearchGeometries.Length; i++)
            {
                if (adfSearchGeometries[i] is ESRI.ArcGIS.ADF.Web.Geometry.Point)
                {
                    ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleMarkerSymbol adfSearchPointSymbol =
                        new ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleMarkerSymbol(
                        System.Drawing.Color.Green, 10, ESRI.ArcGIS.ADF.Web.Display.Symbol.MarkerSymbolType.Star);

                    // Create a graphic element for the search point and add it to the graphics layer
                    inputGraphicElement = new ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicElement(adfSearchGeometries[i],
                            adfSearchPointSymbol);
                }
                else if (adfSearchGeometries[i] is ESRI.ArcGIS.ADF.Web.Geometry.Polyline)
                {
                    ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleLineSymbol adfSearchLineSymbol =
                        new ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleLineSymbol(System.Drawing.Color.Green,
                            4, ESRI.ArcGIS.ADF.Web.Display.Symbol.LineType.Solid);

                    inputGraphicElement = new ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicElement(adfSearchGeometries[i],
                            adfSearchLineSymbol);
                }
                else if (adfSearchGeometries[i] is ESRI.ArcGIS.ADF.Web.Geometry.Polygon)
                {
                    ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleFillSymbol adfSearchFillSymbol =
                        new ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleFillSymbol(System.Drawing.Color.Green,
                            System.Drawing.Color.Black, 50, 20, ESRI.ArcGIS.ADF.Web.Display.Symbol.PolygonFillType.Solid);

                    inputGraphicElement = new GraphicElement(adfSearchGeometries[i],
                            adfSearchFillSymbol);
                }

                inputGraphicsLayer.Add(inputGraphicElement);
            }

            // Get the buffer geometry for the search geometry and distance
            bufferGeometry = this.BufferGeometries(adfSearchGeometries, searchDistance, units);

            // Create a graphic element for the buffer and add it to the graphics layer
            GraphicElement bufferElement = new GraphicElement(bufferGeometry,
                    System.Drawing.Color.Red, 75);
            ElementGraphicsLayer bufferGraphicsLayer =
                this.TaskInputGraphicsResource.GetBufferLayer(this.MapInstance.MapResourceManagerInstance);
            bufferGraphicsLayer.Clear();
            bufferGraphicsLayer.Add(bufferElement);
        }

        return bufferGeometry;
    }    

    // Calculates the buffer of the input geometries
    private ESRI.ArcGIS.ADF.Web.Geometry.Geometry BufferGeometries(
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] adfInputGeometryArray, float bufferDistance, string units)
    {
        // Put the ArcGIS Server SOAP polygon in an ArcGIS Server SOAP geometry array to pass to the
        // buffer operation
        ESRI.ArcGIS.ADF.ArcGISServer.Geometry[] agsSoapInputGeometryArray =
            new ESRI.ArcGIS.ADF.ArcGISServer.Geometry[adfInputGeometryArray.Length];
        for (int i = 0; i < adfInputGeometryArray.Length; i++)
            agsSoapInputGeometryArray[i] =
                ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.Converter.FromAdfGeometry(adfInputGeometryArray[i]);

        // Get a reference to an ArcGIS Server Geometry service
        ESRI.ArcGIS.ADF.ArcGISServer.GeometryServerProxy geometryServerProxy =
            new ESRI.ArcGIS.ADF.ArcGISServer.GeometryServerProxy(this.GeometryServiceUrl);

        // Get a spatial reference in which to perform the buffer operation.  This spatial reference
        // is explicitly intialized based on the user-drawn polygon so that there is minimal projection
        // related buffer distortion
        ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference agsSoapBufferSpatialReference =
            this.CreateOperationSpatialReference(agsSoapInputGeometryArray, geometryServerProxy);

        // Use the units specified by the user to create an ArcGIS Server LinearUnit object
        ESRI.ArcGIS.ADF.ArcGISServer.LinearUnit agsSoapBufferUnits =
            new ESRI.ArcGIS.ADF.ArcGISServer.LinearUnit();
        agsSoapBufferUnits.WKID = this.GetWkidByUnitName(units);
        agsSoapBufferUnits.WKIDSpecified = true;

        // Get the user-specified buffer distance and put it in an array to pass to the buffer
        // operation.  If the user did not specify a distance, initialize the distance to zero.
        double[] bufferDistances = new double[1];
        bufferDistances[0] = System.Convert.ToDouble(bufferDistance);

        ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference agsSoapInputSpatialReference =
            this.GetSpatialReference(agsSoapInputGeometryArray[0]);

        // Execute the buffer operation via the geometry service  
        ESRI.ArcGIS.ADF.ArcGISServer.Geometry[] agsBufferGeometryArray = geometryServerProxy.Buffer(
            agsSoapInputSpatialReference, agsSoapBufferSpatialReference, agsSoapInputSpatialReference,
            bufferDistances, agsSoapBufferUnits, true, agsSoapInputGeometryArray);

        // Retrieve the buffer polygon from the array of result geometries
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry adfBufferGeometry =
            ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.Converter.ToAdfGeometry(agsBufferGeometryArray[0]);

        if (adfBufferGeometry.SpatialReference == null)
            adfBufferGeometry.SpatialReference = adfInputGeometryArray[0].SpatialReference;

        return adfBufferGeometry;
    }

    // Returns the well-known ID of the passed-in unit
    private int GetWkidByUnitName(string unitName)
    {
        int wkid = -1;
        switch (unitName)
        {
            case "Inches":
                wkid = 109009;
                break;
            case "Feet":
                wkid = 9003;
                break;
            case "Yards":
                wkid = 109002;
                break;
            case "Miles":
                wkid = 9035;
                break;
            case "NauticalMiles":
                wkid = 9030;
                break;
            case "Millimeters":
                wkid = 109007;
                break;
            case "Centimeters":
                wkid = 109006;
                break;
            case "Meters":
                wkid = 9001;
                break;
            case "Kilometers":
                wkid = 9036;
                break;
            case "Decimeters":
                wkid = 109005;
                break;
            default:
                // default is Kilometers
                wkid = 9036;
                break;
        }

        return wkid;
    }

    // Creates a spatial reference that minimizes distortion in the vicinity of the passed-in geometriess
    private ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference CreateOperationSpatialReference(
        ESRI.ArcGIS.ADF.ArcGISServer.Geometry[] agsSoapGeometryArray,
        ESRI.ArcGIS.ADF.ArcGISServer.GeometryServerProxy geometryServerProxy)
    {
        // Get the polygon's minimum enclosing rectangle (MER)
        ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN agsSoapBoundingEnvelope = GetBoundingExtent(agsSoapGeometryArray);

        // If the input polygon's spatial reference uses a projected coordinate system, project the MER to a 
        // geographic coordinate system (WGS 1984 in this case).  We do this because the MER's coordinates 
        // will be used to initialize the datum of the operation spatial reference
        ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference agsSoapSpatialReference =
            this.GetSpatialReference(agsSoapGeometryArray[0]);
        if (agsSoapSpatialReference is ESRI.ArcGIS.ADF.ArcGISServer.ProjectedCoordinateSystem)
        {
            // Create an ArcGIS Server spatial reference initalized to use the WGS 1984 coordinate system
            ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference agsSoapGeographicSpatialReference =
                new ESRI.ArcGIS.ADF.ArcGISServer.GeographicCoordinateSystem();
            agsSoapGeographicSpatialReference.WKID = 4326;
            agsSoapGeographicSpatialReference.WKIDSpecified = true;

            // Place the input MER in an array for the project operation
            ESRI.ArcGIS.ADF.ArcGISServer.Geometry[] agsSoapInputGeometryArray =
                new ESRI.ArcGIS.ADF.ArcGISServer.Geometry[1];
            agsSoapInputGeometryArray[0] = agsSoapBoundingEnvelope;

            // Execute the projection
            ESRI.ArcGIS.ADF.ArcGISServer.Geometry[] agsSoapOutputGeometryArray =
                geometryServerProxy.Project(agsSoapSpatialReference, agsSoapGeographicSpatialReference,
                true, null, null, agsSoapInputGeometryArray);

            // Retrieve the projected MER from the results array
            agsSoapBoundingEnvelope = agsSoapOutputGeometryArray[0] as ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN;
        }

        // Get the latitude (Y coordinate) at the center of the MER
        double centerLatitude = agsSoapBoundingEnvelope.YMax -
            (agsSoapBoundingEnvelope.YMax - agsSoapBoundingEnvelope.YMin) / 2;

        // Create the definition string for the operation spatial reference's coordinate system.  We will use 
        // the Hotine Oblique Mercator coordinate system because it lends itself well to minimizing operational
        // distortion anywhere on the earth
        string hotineObliqueMercatorDefinition = @"
            PROJCS[""VN_2000_Ninh_Thuan_3deg"",
            GEOGCS[""GCS_VN_2000"",
            DATUM[""D_Vietnam_2000"",
            SPHEROID[""WGS_1984"",
            6378137.0,298.257223563]],
            PRIMEM[""Greenwich"",0.0],
            UNIT[""Degree"",0.0174532925199433]],
            PROJECTION[""Transverse_Mercator""],
            PARAMETER[""False_Easting"",500000.0],
            PARAMETER[""False_Northing"",0.0],
            PARAMETER[""Central_Meridian"",108.25],
            PARAMETER[""Scale_Factor"",0.9999],
            PARAMETER[""Latitude_Of_Origin"",0.0],
            UNIT[""Meter"",1.0]]";

        // Specify the relevant coordinates of the MER for the coordinate system's datum parameters
        string customHotineObliqueCylindricalMercator = string.Format(
            hotineObliqueMercatorDefinition, agsSoapBoundingEnvelope.YMin,
            agsSoapBoundingEnvelope.YMax, agsSoapBoundingEnvelope.XMin,
            agsSoapBoundingEnvelope.XMax, centerLatitude);

        // Create the spatial reference
        ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference agsSoapBufferSpatialReference =
            geometryServerProxy.FindSRByWKT(customHotineObliqueCylindricalMercator, null, true, true);

        return agsSoapBufferSpatialReference;
    }

    // Returns the spatial reference of an ArcGIS Server SOAP Geometry
    private ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference GetSpatialReference(
        ESRI.ArcGIS.ADF.ArcGISServer.Geometry agsSoapGeometry)
    {
        ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference agsSoapSpatialReference = null;
        if (agsSoapGeometry is ESRI.ArcGIS.ADF.ArcGISServer.PolygonN)
        {
            ESRI.ArcGIS.ADF.ArcGISServer.PolygonN agsSoapPolygon = agsSoapGeometry as
                ESRI.ArcGIS.ADF.ArcGISServer.PolygonN;
            agsSoapSpatialReference = agsSoapPolygon.SpatialReference;
        }
        else if (agsSoapGeometry is ESRI.ArcGIS.ADF.ArcGISServer.PolylineN)
        {
            ESRI.ArcGIS.ADF.ArcGISServer.PolylineN agsSoapPolyline = agsSoapGeometry as
                ESRI.ArcGIS.ADF.ArcGISServer.PolylineN;
            agsSoapSpatialReference = agsSoapPolyline.SpatialReference;
        }
        else if (agsSoapGeometry is ESRI.ArcGIS.ADF.ArcGISServer.PointN)
        {
            ESRI.ArcGIS.ADF.ArcGISServer.PointN agsSoapPoint = agsSoapGeometry as
                ESRI.ArcGIS.ADF.ArcGISServer.PointN;
            agsSoapSpatialReference = agsSoapPoint.SpatialReference;
        }

        return agsSoapSpatialReference;
    }

    // Gets the bounding extent of the passed-in ArcGIS Server SOAP geometries
    private ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN GetBoundingExtent(
        ESRI.ArcGIS.ADF.ArcGISServer.Geometry[] agsSoapGeometryArray)
    {
        // Instantiate an envelope with max values minimized and min values maximized
        ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN agsSoapBoundingBox =
            new ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN();
        agsSoapBoundingBox.XMin = double.MaxValue;
        agsSoapBoundingBox.XMax = double.MinValue;
        agsSoapBoundingBox.YMin = double.MaxValue;
        agsSoapBoundingBox.YMax = double.MinValue;

        for (int i = 0; i < agsSoapGeometryArray.Length; i++)
        {
            ESRI.ArcGIS.ADF.ArcGISServer.Geometry agsSoapGeometry = agsSoapGeometryArray[i];
            if (agsSoapGeometry is ESRI.ArcGIS.ADF.ArcGISServer.PolygonN)
            {
                ESRI.ArcGIS.ADF.ArcGISServer.PolygonN agsSoapPolygon = agsSoapGeometry as
                    ESRI.ArcGIS.ADF.ArcGISServer.PolygonN;
                // Iterate through all the polygon's vertices
                for (int j = 0; j < agsSoapPolygon.RingArray.Length; j++)
                {
                    ESRI.ArcGIS.ADF.ArcGISServer.Ring agsSoapRing = agsSoapPolygon.RingArray[j];
                    for (int k = 0; k < agsSoapRing.PointArray.Length; k++)
                    {
                        // For each vertex, expand the bounds of the minimum enclosing rectangle with the 
                        // vertex's coordinates if they fall outside the rectangle's current bounds
                        ESRI.ArcGIS.ADF.ArcGISServer.PointN agsSoapCurrentPoint = agsSoapRing.PointArray[k]
                            as ESRI.ArcGIS.ADF.ArcGISServer.PointN;

                        if (agsSoapCurrentPoint.X < agsSoapBoundingBox.XMin)
                            agsSoapBoundingBox.XMin = agsSoapCurrentPoint.X;
                        else if (agsSoapCurrentPoint.X > agsSoapBoundingBox.XMax)
                            agsSoapBoundingBox.XMax = agsSoapCurrentPoint.X;

                        if (agsSoapCurrentPoint.Y < agsSoapBoundingBox.YMin)
                            agsSoapBoundingBox.YMin = agsSoapCurrentPoint.Y;
                        else if (agsSoapCurrentPoint.Y > agsSoapBoundingBox.YMax)
                            agsSoapBoundingBox.YMax = agsSoapCurrentPoint.Y;
                    }
                }
            }
            else if (agsSoapGeometry is ESRI.ArcGIS.ADF.ArcGISServer.PolylineN)
            {
                ESRI.ArcGIS.ADF.ArcGISServer.PolylineN agsSoapPolyline = agsSoapGeometry as
                    ESRI.ArcGIS.ADF.ArcGISServer.PolylineN;
                // Iterate through all the polyline's vertices
                for (int j = 0; j < agsSoapPolyline.PathArray.Length; j++)
                {
                    ESRI.ArcGIS.ADF.ArcGISServer.Path agsSoapPath = agsSoapPolyline.PathArray[j];
                    for (int k = 0; k < agsSoapPath.PointArray.Length; k++)
                    {
                        // For each vertex, expand the bounds of the minimum enclosing rectangle with the 
                        // vertex's coordinates if they fall outside the rectangle's current bounds
                        ESRI.ArcGIS.ADF.ArcGISServer.PointN agsSoapCurrentPoint = agsSoapPath.PointArray[k]
                            as ESRI.ArcGIS.ADF.ArcGISServer.PointN;

                        if (agsSoapCurrentPoint.X < agsSoapBoundingBox.XMin)
                            agsSoapBoundingBox.XMin = agsSoapCurrentPoint.X;
                        else if (agsSoapCurrentPoint.X > agsSoapBoundingBox.XMax)
                            agsSoapBoundingBox.XMax = agsSoapCurrentPoint.X;

                        if (agsSoapCurrentPoint.Y < agsSoapBoundingBox.YMin)
                            agsSoapBoundingBox.YMin = agsSoapCurrentPoint.Y;
                        else if (agsSoapCurrentPoint.Y > agsSoapBoundingBox.YMax)
                            agsSoapBoundingBox.YMax = agsSoapCurrentPoint.Y;
                    }
                }
            }
            else if (agsSoapGeometry is ESRI.ArcGIS.ADF.ArcGISServer.PointN)
            {
                ESRI.ArcGIS.ADF.ArcGISServer.PointN agsSoapPoint = agsSoapGeometry
                    as ESRI.ArcGIS.ADF.ArcGISServer.PointN;

                agsSoapBoundingBox.XMin = agsSoapPoint.X - .1;
                agsSoapBoundingBox.XMax = agsSoapPoint.X + .1;
                agsSoapBoundingBox.YMin = agsSoapPoint.Y - .1;
                agsSoapBoundingBox.YMax = agsSoapPoint.Y + .1;
            }
        }

        return agsSoapBoundingBox;
    }

    #endregion
    #endregion
}

#region Classes for Tools
// Allows specification of search area by user-drawn point
public class SearchAreaByPoint : ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IMapServerToolAction,
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IToolbarItemSetup
{
    // Instance variable referencing the toolbar item encapsulating the tool
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo _toolbarItemInfo;

    #region IMapServerToolAction Members

    // Fires when the tool is active and the map is clicked
    public void ServerAction(ESRI.ArcGIS.ADF.Web.UI.WebControls.ToolEventArgs toolEventArgs)
    {
        // Get a reference to the FindNearTask containing the tool
        UserControls_FindNearTask findNearTask = Utility.GetContainer(_toolbarItemInfo.Toolbar,
            typeof(UserControls_FindNearTask)) as UserControls_FindNearTask;
        // Make sure the task was found
        if (findNearTask == null)
            return;

        // Get the user-drawn point and put it in a geometry array
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapPointEventArgs mapPointEventArgs = toolEventArgs as
            ESRI.ArcGIS.ADF.Web.UI.WebControls.MapPointEventArgs;
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] inputGeometries = new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[1];
        inputGeometries[0] = mapPointEventArgs.MapPoint as ESRI.ArcGIS.ADF.Web.Geometry.Geometry;
        findNearTask.OnToolbarItemAction(toolEventArgs.ToolName, inputGeometries);
    }

    #endregion

    #region IToolbarItemSetup Members

    // We implement IToolbarItemSetup to gain easy access to the tool via the Initialize method.
    public void Initialize(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo)
    {
        _toolbarItemInfo = toolbarItemInfo;
    }

    public void Unload(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo) { }

    #endregion
}

// Allows specification of search area by user-drawn polyline
public class SearchAreaByPolyline : ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IMapServerToolAction,
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IToolbarItemSetup
{
    // Instance variable referencing the toolbar item encapsulating the tool
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo _toolbarItemInfo;

    #region IMapServerToolAction Members

    // Fires when the tool is active and the user has finished drawing a polyline on the map
    public void ServerAction(ESRI.ArcGIS.ADF.Web.UI.WebControls.ToolEventArgs toolEventArgs)
    {
        // Get a reference to the containing FindNearTask
        UserControls_FindNearTask findNearTask = Utility.GetContainer(_toolbarItemInfo.Toolbar,
            typeof(UserControls_FindNearTask)) as UserControls_FindNearTask;

        // Get the geometry of the user-drawn polyline and put it in a geometry array
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapPolylineEventArgs mapPolylineEventArgs =
            toolEventArgs as ESRI.ArcGIS.ADF.Web.UI.WebControls.MapPolylineEventArgs;
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] inputGeometries = new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[1];
        inputGeometries[0] = mapPolylineEventArgs.MapPolyline as ESRI.ArcGIS.ADF.Web.Geometry.Geometry;
        findNearTask.OnToolbarItemAction(toolEventArgs.ToolName, inputGeometries);
    }
    #endregion

    #region IToolbarItemSetup Members

    // We implement IToolbarItemSetup to gain easy access to the tool via the Initialize method.
    public void Initialize(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo)
    {
        _toolbarItemInfo = toolbarItemInfo;
    }

    public void Unload(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo) { }

    #endregion
}

// Allows specification of search area by user-drawn polygon
public class SearchAreaByPolygon : ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IMapServerToolAction,
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IToolbarItemSetup
{
    // Instance variable referencing the toolbar item encapsulating the tool
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo _toolbarItemInfo;

    #region IMapServerToolAction Members

    public void ServerAction(ESRI.ArcGIS.ADF.Web.UI.WebControls.ToolEventArgs toolEventArgs)
    {
        // Get a reference to the containing FindNearTask
        UserControls_FindNearTask findNearTask = Utility.GetContainer(_toolbarItemInfo.Toolbar,
            typeof(UserControls_FindNearTask)) as UserControls_FindNearTask;

        // Retrieve the user-drawn polygon and put it in a geometry array
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapPolygonEventArgs mapPolygonEventArgs =
            toolEventArgs as ESRI.ArcGIS.ADF.Web.UI.WebControls.MapPolygonEventArgs;
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] inputGeometries = new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[1];
        inputGeometries[0] = mapPolygonEventArgs.MapPolygon as ESRI.ArcGIS.ADF.Web.Geometry.Geometry;
        findNearTask.OnToolbarItemAction(toolEventArgs.ToolName, inputGeometries);
    }
    #endregion

    #region IToolbarItemSetup Members

    // We implement IToolbarItemSetup to gain easy access to the tool via the Initialize method.
    public void Initialize(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo)
    {
        _toolbarItemInfo = toolbarItemInfo;
    }

    public void Unload(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo) { }

    #endregion
}

// Allows specification of search area by selecting a task result.  All the implementation logic
// is handled in the Node_Clicked event of the TaskResults control buddied to the parent FindNearTask.
// This is necessary because the tool's ServerAction only fires when the Map is clicked.  We use a
// Web ADF Tool nonetheless because it provides a simple framework for integration with a Web ADF
// Toolbar and task.
public class SearchAreaByTaskResult : ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IMapServerToolAction, IToolbarItemSetup
{
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo _toolbarItemInfo;
    #region IMapServerToolAction Members

    public void ServerAction(ESRI.ArcGIS.ADF.Web.UI.WebControls.ToolEventArgs args)
    {
        UserControls_FindNearTask findNearTask = Utility.GetContainer(args.Control,
            typeof(UserControls_FindNearTask)) as UserControls_FindNearTask;
        if (findNearTask == null)
            return;
        findNearTask.OnToolbarItemAction(args.ToolName, null);
    }
    #endregion

    #region IToolbarItemSetup Members

    // We implement IToolbarItemSetup to gain easy access to the tool via the Initialize method.
    public void Initialize(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo)
    {
        _toolbarItemInfo = toolbarItemInfo;
    }

    public void Unload(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo) { }
    #endregion
}

// Allows specification of a search area by selecting features on the map
public class SearchAreaBySelection : ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IMapServerToolAction, ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IToolbarItemSetup
{
    // Instance variable referencing the toolbar item encapsulating the tool
    ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo _toolbarItemInfo;

    #region IMapServerToolAction Members

    public void ServerAction(ESRI.ArcGIS.ADF.Web.UI.WebControls.ToolEventArgs toolEventArgs)
    {
        // Get a reference to the containing FindNearTask
        UserControls_FindNearTask findNearTask = Utility.GetContainer(_toolbarItemInfo.Toolbar,
            typeof(UserControls_FindNearTask)) as UserControls_FindNearTask;

        // Get a reference to the map control on which the tool was executed
        Map adfMap = (Map)toolEventArgs.Control;

        // Get the map extent of the rectangle drawn on the map
        MapRectangleEventArgs mapRectangleEventArgs = (MapRectangleEventArgs)toolEventArgs;
        ESRI.ArcGIS.ADF.Web.Geometry.Envelope adfEnvelope = mapRectangleEventArgs.MapExtent;
        // Get the user-specified tool parameters specified via the page's interface

        string selectionInput = null;  
        if (adfMap.Page.IsCallback)
        {
            // Since the page is using the callback framework, the tool parameters have been packaged 
            // along with the callback parameters.  This is done via custom javascript.
            string callbackArguments = adfMap.Page.Request.Params["__CALLBACKPARAM"];
            System.Collections.Specialized.NameValueCollection callbackArgsCollection =
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(
                callbackArguments);
            selectionInput = callbackArgsCollection["SelectionLayer"];
        }
        else
        {            
            selectionInput = (findNearTask.FindControl("ddlSelectionLayers") as DropDownList).SelectedValue;
        }

        string[] selectionInputArray = selectionInput.Split(',');

        // Get the name of the resource on which to perform the selection
        string resourceName = selectionInputArray[0];

        // Get a reference to the resource
        ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality commonMapFunctionality =
            adfMap.GetFunctionality(resourceName);
        ESRI.ArcGIS.ADF.Web.DataSources.IGISResource gisResource = commonMapFunctionality.Resource;

        // Create a query functionality to use in querying the resource
        ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality commonQueryFunctionality =
            (ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality)
            gisResource.CreateFunctionality(
            typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality), null);

        string selectionLayerID = selectionInputArray[1];

        // Set-up a spatial filter to use in querying the resource
        ESRI.ArcGIS.ADF.Web.SpatialFilter adfSpatialFilter =
            new ESRI.ArcGIS.ADF.Web.SpatialFilter();
        adfSpatialFilter.ReturnADFGeometries = true;
        adfSpatialFilter.MaxRecords = 100;
        adfSpatialFilter.Geometry = adfEnvelope;

        // Query the selection layer with the user-drawn rectangle
        System.Data.DataTable resultsDataTable = commonQueryFunctionality.Query(
            commonMapFunctionality.Name, selectionLayerID, adfSpatialFilter);

        // Convert the results data table to a graphics layer
        ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer resultsGraphicsLayer =
            ESRI.ArcGIS.ADF.Web.Converter.ToGraphicsLayer(resultsDataTable);

        // Put the geometries of the results features in a geometry array
        ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] inputGeometries =
            new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[resultsGraphicsLayer.Rows.Count];
        for (int i = 0; i < resultsGraphicsLayer.Rows.Count; i++)
            inputGeometries[i] = resultsGraphicsLayer.GeometryFromRow(resultsGraphicsLayer.Rows[i]);

        findNearTask.OnToolbarItemAction(toolEventArgs.ToolName, inputGeometries);
    }
    #endregion


    #region IToolbarItemSetup Members

    // We implement IToolbarItemSetup to gain easy access to the tool via the Initialize method.
    public void Initialize(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo)
    {
        _toolbarItemInfo = toolbarItemInfo;
    }

    public void Unload(ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo) { }

    #endregion
}

public class ClearInput : ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IMapServerCommandAction
{
    #region IMapServerCommandAction Members

    void ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.IServerAction.ServerAction(
        ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools.ToolbarItemInfo toolbarItemInfo)
    {
        // Get a reference to the containing FindNearTask and call the method to clear all task input
        UserControls_FindNearTask findNearTask = Utility.GetContainer(toolbarItemInfo.Toolbar,
            typeof(UserControls_FindNearTask)) as UserControls_FindNearTask;
        findNearTask.OnToolbarItemAction(toolbarItemInfo.Name, null);        
    }

    #endregion
}

public class Utility
{
    // Traverses the ancestors of the passed-in control until the first ancestor of the passed-in type is found
    public static System.Web.UI.Control GetContainer(System.Web.UI.Control control, System.Type typeOfContainer)
    {
        System.Web.UI.Control containerControl = null;
        if (control.Parent != null)
        {
            if (typeOfContainer.IsAssignableFrom(control.Parent.GetType()))
                containerControl = control.Parent;
            else
                containerControl = GetContainer(control.Parent, typeOfContainer);
        }

        return containerControl;
    }
}

// Manages task input parameters
internal class InputParameters
{
    // Array to store input geometries.  Used when the user wants to combine geometries.
    private ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] _userInputGeometries = null;

    // Name of the map resource containing the layer to search
    public string SearchResource = null;
    // Name of the layer to search
    public string SearchLayer = null;
    // Search distance
    public float SearchDistance = 1000;
    // Search units
    public string Units = null;
    // Search geometry.  This is the user input geometries buffered by the search distance.
    public ESRI.ArcGIS.ADF.Web.Geometry.Geometry BufferGeometry = null;
    // Whether or not to combine new input geometries with those previously defined
    public bool AddToInputGeometry = true;

    // Provides read access to the geometries to search near, as defined by the user
    public ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] UserInputGeometries
    {
        get { return _userInputGeometries; }
    }

    // Defines the geometries to search near.  If AddToInputGeometry is true, the passed-in geometries
    // are combined with those already defined.  Otherwise, the defined input geometries are overwritten.
    public void SetUserInputGeometries(ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] inputGeometries)
    {
        // Check whether to combine the passed-in geometries with those already defined
        if (this.AddToInputGeometry && _userInputGeometries != null)
        {
            // Create a new geometry array that's large enough to hold both the defined geometries and those
            // passed-in
            ESRI.ArcGIS.ADF.Web.Geometry.Geometry[] combinedGeometries =
                new ESRI.ArcGIS.ADF.Web.Geometry.Geometry[inputGeometries.Length + _userInputGeometries.Length];

            // Copy the previously defined geometries to the new array
            _userInputGeometries.CopyTo(combinedGeometries, 0);
            // Add the passed-in geometries to the end of the new array
            for (int i = _userInputGeometries.Length; i < combinedGeometries.Length; i++)
                combinedGeometries[i] = inputGeometries[i - _userInputGeometries.Length];

            // Overwrite the previous geometry array with the combined array
            _userInputGeometries = combinedGeometries;
        }
        else
        {
            // Overwrite the previous geometry array or initialize it with the passed-in array
            _userInputGeometries = inputGeometries;
        }
    }
}

// Manages the map resource used by FindNearTask to store its user input and buffer GraphicsLayers
internal class GraphicsResource
{
    // Construtor that performs initialization logic
    public GraphicsResource(string resourceName, ESRI.ArcGIS.ADF.Web.UI.WebControls.Map mapInstance)
    {
        // Set the resource name and call the initialization method
        this.ResourceName = resourceName;
        this.Initialize(mapInstance);
    }

    public string ResourceName;

    #region Public Methods

    // Retrieves the user input GraphicsLayer
    public ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer GetUserInputLayer(
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceManager mapResourceManager)
    {
        return this.GetLayer(mapResourceManager, "User Input Graphics Layer");
    }

    // Retrieves the buffer GraphicsLayer
    public ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer GetBufferLayer(
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceManager mapResourceManager)
    {
        return this.GetLayer(mapResourceManager, "Buffer Graphics Layer");
    }

    #endregion

    #region Private Methods

    // Creates the graphics resource and graphics layers used by FindNearTask
    private void Initialize(ESRI.ArcGIS.ADF.Web.UI.WebControls.Map mapInstance)
    {
        // Attempt to retrieve the resource item referred to the by the ResourceName property.
        // If it is not found, execute logic to create and populate it.
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem resourceItem =
            mapInstance.MapResourceManagerInstance.ResourceItems.Find(ResourceName);
        if (resourceItem == null)
        {
            // Create a garphics resource item and initialize its properties
            resourceItem = new ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem();
            string resourceDefinition = @"<Definition DataSourceDefinition=""In Memory"" DataSourceType=""GraphicsLayer"" 
                    Identity="""" ResourceDefinition="""" DataSourceShared=""True""></Definition>";
            resourceItem.Definition = new ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDefinition(resourceDefinition);
            resourceItem.Name = ResourceName;
            resourceItem.DisplaySettings = new ESRI.ArcGIS.ADF.Web.DisplaySettings();
            resourceItem.DisplaySettings.Visible = true;
            resourceItem.DisplaySettings.Transparency = 25;
            resourceItem.DisplaySettings.DisplayInTableOfContents = false;            

            resourceItem.DisplaySettings.ImageDescriptor = new ESRI.ArcGIS.ADF.Web.ImageDescriptor(
                ESRI.ArcGIS.ADF.Web.UI.WebControls.Converter.FromWebImageFormat(mapInstance.ImageFormat),
                (int)mapInstance.Width.Value, (int)mapInstance.Height.Value);
            mapInstance.MapResourceManagerInstance.ResourceItems.Insert(0, resourceItem);
            resourceItem.DisplaySettings.ImageDescriptor.TransparentBackground = true;
            resourceItem.DisplaySettings.ImageDescriptor.TransparentColor =
                System.Drawing.Color.FromArgb(1, 1, 1);

            // Create the Web ADF MapResource for the resource item and get a reference to it
            ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource graphicsMapResource =
                resourceItem.CreateResource() as ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource;

            // Create a graphics dataset to hold FindNearTask's GraphicsLayers
            graphicsMapResource.Graphics = new ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet();
            graphicsMapResource.Graphics.DataSetName = "FindNearTask Input Graphics";

            // Create the user input GraphicsLayer and add it to the dataset
            ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer elementGraphicsLayer =
                new ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer();
            elementGraphicsLayer.TableName = "User Input Graphics Layer";
            graphicsMapResource.Graphics.Tables.Add(elementGraphicsLayer);

            // Create the buffer GraphicsLayer and add it to the dataset
            elementGraphicsLayer = new ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer();
            elementGraphicsLayer.TableName = "Buffer Graphics Layer";
            graphicsMapResource.Graphics.Tables.Add(elementGraphicsLayer);

            // Initialize the resource and functionalities referenced by the resource item
            if (mapInstance.MapResourceManagerInstance.Initialized)
                resourceItem.InitializeResource();
            if (mapInstance.InitializedFunctionalities)
                mapInstance.InitializeFunctionality(resourceItem);
        }
    }

    // Retrieves the GraphicsLayer having the passed in name
    private ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer GetLayer(
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceManager mapResourceManager, string layerName)
    {
        ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer elementGraphicsLayer = null;

        // Get the resource item corresponding to the ResourceName property and make sure it's 
        // initialized
        ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem resourceItem =
            mapResourceManager.ResourceItems.Find(this.ResourceName);
        
        // Get the graphics resource underlying the resource item
        ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource graphicsMapResource =
            resourceItem.Resource as ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource;

        // Make sure the graphics resource contains the requested layer
        if (graphicsMapResource != null && graphicsMapResource.Graphics != null &&
            graphicsMapResource.Graphics.Tables[layerName] != null)
        {
            // Get the GraphicsLayer
            elementGraphicsLayer = graphicsMapResource.Graphics.Tables[layerName] as
                ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer;
        }

        return elementGraphicsLayer;
    }

    #endregion

}
#endregion